package display.client;

import com.google.gwt.user.client.Random;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

import track.ObjectLocation;

public class BattlefieldData {
    // Minimum and maximum altitude for airborne tracked objects.
    public static int MIN_ALTITUDE = 15;
    public static int MAX_ALTITUDE = 100;

    // For a new tracked object, the chance (0-1) that it will be surface-level
    // versus airborne.
    public static float SURFACE_LEVEL_CHANCE = 0.4F;

    // Maximum and minimum velocity values for a new tracked object.
    public static int MIN_DELTA_X = -2;
    public static int MAX_DELTA_X = 2;
    public static int MIN_DELTA_Y = -2;
    public static int MAX_DELTA_Y = 2;

    // Current number of tracked objects (and default).
    public static int m_currentTracked = 10;

    // Absolute minimum of tracked objects.
    public static int MINIMUM_TRACKED = 1;

    // Absolute maximum number of tracked objects.
    public static int MAXIMUM_TRACKED = 50;

    // Current delay between battlefield updates (and default).
    public static int m_battlefieldUpdateDelay = 100;

    // Minimum delay between battlefield updates.
    public static int BATTLEFIELD_MINIMUM_UPDATE_DELAY = 100;

    // Minimum delay between battlefield updates.
    public static int BATTLEFIELD_MAXIMUM_UPDATE_DELAY = 10000;

    // Sequentially-allocated unique track ID.
    private static int TRACK_ID;

    // Default transport priority for the ObjectLocation data writer.
    public static int DEFAULT_TRANSPORT_PRIORITY = 3;

    // Default latency budget for the ObjectLocation data writer.
    public static double DEFAULT_LATENCY_BUDGET = 0D;

    // ObjectLocation message padding.
    public static int DEFAULT_PADDING = 0;
    public static String m_padding;

    // Collection of tracked objects; key is the track ID.
    private static HashMap<Integer, TrackedObject> m_tracked = new HashMap<Integer, TrackedObject> ();

    private static Vector<Integer> disposedIds = new Vector<Integer>();
    
    /**
     * Data for a tracked object.
     */
    static class TrackedObject {
        // Track ID. While this is also held in the ObjectLocation instance, the
        // battlefield assigns an ID separately from creating and registering
        // the
        // topic instance.
        int m_id;

        // Current position.
        int m_x;
        int m_y;
        int m_z;

        // Velocity.
        int m_deltax;
        int m_deltay;

        // Battlefield topic instance and associated handle.
        ObjectLocation m_objLoc = null;
        long m_handle = 0L;

        public String toString() {
            StringBuffer sb = new StringBuffer("ID: ");
            sb.append(m_id);
            sb.append(", POS: ");
            sb.append(m_x);
            sb.append(" (");
            sb.append(m_deltax);
            sb.append("), ");
            sb.append(m_y);
            sb.append(" (");
            sb.append(m_deltay);
            sb.append("), ");
            sb.append(m_z);

            return sb.toString();
        }
    }

    /**
     * Creates a new tracked object. New objects always appear at an edge, and
     * have a velocity that will take them into the battlefield.
     */
    public static void createTracked() {
        TrackedObject tracked = new TrackedObject();
        tracked.m_id = TRACK_ID++;

        // Ensure at least one velocity is not zero.
        int deltax = 0;
        int deltay = 0;
        while (deltax == 0 && deltay == 0) {
            deltax = MIN_DELTA_X
            + (int) (Random.nextDouble() * (MAX_DELTA_X - MIN_DELTA_X));
            deltay = MIN_DELTA_Y
            + (int) (Random.nextDouble() * (MAX_DELTA_Y - MIN_DELTA_Y));
        }

        // Assign a random starting point and appropriate velocity.
        if (Random.nextDouble() < 0.50) {
            // Random X position, minimum/maximum Y position.
            tracked.m_x = Constants.BATTLEFIELD_MIN_X
            + (int) (Random.nextDouble() * (Constants.BATTLEFIELD_MAX_X - Constants.BATTLEFIELD_MIN_X));
            tracked.m_y = (Random.nextDouble() < 0.5 ? Constants.BATTLEFIELD_MIN_Y
                    : Constants.BATTLEFIELD_MAX_Y);

            tracked.m_deltax = deltax;
            tracked.m_deltay = (tracked.m_y == Constants.BATTLEFIELD_MIN_Y ? Math
                    .abs(deltay) : Math.abs(deltay) * -1);
        } else {
            // Minimum/maximum X position, random Y position.
            tracked.m_x = (Random.nextDouble() < 0.5 ? Constants.BATTLEFIELD_MIN_X
                    : Constants.BATTLEFIELD_MAX_X);
            tracked.m_y = Constants.BATTLEFIELD_MIN_Y
            + (int) (Random.nextDouble() * (Constants.BATTLEFIELD_MAX_Y - Constants.BATTLEFIELD_MIN_Y));

            tracked.m_deltax = (tracked.m_x == Constants.BATTLEFIELD_MIN_X ? Math
                    .abs(deltax) : Math.abs(deltax) * -1);
            tracked.m_deltay = deltay;
        }

        // Assign a random altitude.
        if (Random.nextDouble() < SURFACE_LEVEL_CHANCE) {
            tracked.m_z = 0;
        } else {
            tracked.m_z = MIN_ALTITUDE
            + (int) (Random.nextDouble() * (MAX_ALTITUDE - MIN_ALTITUDE));
        }
        
        // Create and register the ObjectLocation
        // instance if needed.
        if (tracked.m_objLoc == null) {
            tracked.m_objLoc = new ObjectLocation();
            tracked.m_objLoc.trackId = tracked.m_id;
            tracked.m_objLoc.padding = m_padding;
        }

        // Update the position information and publish
        // to the topic.
        tracked.m_objLoc.x = tracked.m_x;
        tracked.m_objLoc.y = tracked.m_y;
        tracked.m_objLoc.z = tracked.m_z;

        // Add the object to the map.
        Object old = m_tracked.put(new Integer(tracked.m_id), tracked);
        if (old != null) {
            System.out.println("Warning: new tracked object " + tracked + " replaced "
                    + old + " in the map");
        }
    }

    public static void updateObjects() {
        synchronized (m_tracked) {
            Set<Integer> keys = m_tracked.keySet();
            Iterator<Integer> keysIter = keys.iterator();
            while (keysIter.hasNext()) {
                Integer key = (Integer) keysIter.next();
                TrackedObject tracked = (TrackedObject) m_tracked.get(key);

                // Update the position.
                tracked.m_x += tracked.m_deltax;
                tracked.m_y += tracked.m_deltay;

                if (tracked.m_x < Constants.BATTLEFIELD_MIN_X
                    || tracked.m_x > Constants.BATTLEFIELD_MAX_X
                    || tracked.m_y < Constants.BATTLEFIELD_MIN_Y
                    || tracked.m_y > Constants.BATTLEFIELD_MAX_Y) {
                    // Passed beyond the battlefield boundary:
                    // remove the object.
                    keysIter.remove();
                    //System.out.println ("BattlefieldData dispose object " + key);
                    SensorData.disposePointTrackWithId(key);
                    IdentificationData.disposeTrackStateWithId(key);
                    disposedIds.add(key);
                } else {
                    // Object still on the battlefield; publish the
                    // information to the battlefield topic.

                    // Create and register the ObjectLocation
                    // instance if needed.
                    if (tracked.m_objLoc == null) {
                        tracked.m_objLoc = new ObjectLocation();
                        tracked.m_objLoc.trackId = tracked.m_id;
                        tracked.m_objLoc.padding = m_padding;
                    }

                    // Update the position information and publish
                    // to the topic.
                    tracked.m_objLoc.x = tracked.m_x;
                    tracked.m_objLoc.y = tracked.m_y;
                    tracked.m_objLoc.z = tracked.m_z;
                }
            }

            // Ensure we track the requested number of objects.
            while (m_tracked.size() < m_currentTracked) {
                createTracked();
            }
        }        
    }
    
    public static ObjectLocation[] getObjectLocations() {
        updateObjects ();
        ObjectLocation[] ols = new ObjectLocation[m_currentTracked];
        int index = 0;
        synchronized (m_tracked) {
            Set<Integer> keys = m_tracked.keySet();
            Iterator<Integer> keysIter = keys.iterator();
            while (keysIter.hasNext()) {
                Integer key = (Integer) keysIter.next();
                TrackedObject tracked = (TrackedObject) m_tracked.get(key);
                ols[index] = tracked.m_objLoc;
                index++;
            }
        }
        return ols;
    }
    
    public static Integer[] getDisposedObjectLocationIds() {
        Integer[] ids = new Integer[disposedIds.size()];
        ids = disposedIds.toArray(ids);
        disposedIds.clear();
        return ids;
    }

    public static ObjectLocation[] getObjectLocationsWithoutUpdate() {
        ObjectLocation[] ols = new ObjectLocation[m_currentTracked];
        int index = 0;
        synchronized (m_tracked) {
            Set<Integer> keys = m_tracked.keySet();
            Iterator<Integer> keysIter = keys.iterator();
            while (keysIter.hasNext()) {
                Integer key = (Integer) keysIter.next();
                TrackedObject tracked = (TrackedObject) m_tracked.get(key);
                ols[index] = tracked.m_objLoc;
                index++;
            }
        }
        return ols;
    }
}
